///\todo upgrade

//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef _BUOLA_IMAGE_VERTEX_CBEZIER_H_
#define _BUOLA_IMAGE_VERTEX_CBEZIER_H_

#include <buola/image.h>

namespace buola { namespace img {

class CBezier
{
public:
    CBezier()=default;

    CBezier(const CPoint_d &p1,const CPoint_d &p2,const CPoint_d &p3,const CPoint_d &p4)
        :   m1(p1)
        ,   m2(p2)
        ,   m3(p3)
        ,   m4(p4)
    {}
    
    inline CPoint_d MidPoint() const
    {
        return (m1+m4+3.0*(m2+m3));
    }

///\todo
/*    
    inline QLineF QBezier::midTangent() const
    {
        QPointF mid = midPoint();
        QLineF dir(QLineF(x1, y1, x2, y2).pointAt(0.5), QLineF(x3, y3, x4, y4).pointAt(0.5));
        return QLineF(mid.x() - dir.dx(), mid.y() - dir.dy(),
                    mid.x() + dir.dx(), mid.y() + dir.dy());
    }

    inline QLineF QBezier::startTangent() const
    {
        QLineF tangent(pt1(), pt2());
        if (tangent.isNull())
            tangent = QLineF(pt1(), pt3());
        if (tangent.isNull())
            tangent = QLineF(pt1(), pt4());
        return tangent;
    }

    inline QLineF QBezier::endTangent() const
    {
        QLineF tangent(pt4(), pt3());
        if (tangent.isNull())
            tangent = QLineF(pt4(), pt2());
        if (tangent.isNull())
            tangent = QLineF(pt4(), pt1());
        return tangent;
    }
    
    inline void QBezier::coefficients(qreal t, qreal &a, qreal &b, qreal &c, qreal &d)
    {
        qreal m_t = 1. - t;
        b = m_t * m_t;
        c = t * t;
        d = c * t;
        a = b * m_t;
        b *= 3. * t;
        c *= 3. * m_t;
    }

    inline QPointF QBezier::pointAt(qreal t) const
    {
        // numerically more stable:
        qreal x, y;

        qreal m_t = 1. - t;
        {
            qreal a = x1*m_t + x2*t;
            qreal b = x2*m_t + x3*t;
            qreal c = x3*m_t + x4*t;
            a = a*m_t + b*t;
            b = b*m_t + c*t;
            x = a*m_t + b*t;
        }
        {
            qreal a = y1*m_t + y2*t;
            qreal b = y2*m_t + y3*t;
            qreal c = y3*m_t + y4*t;
            a = a*m_t + b*t;
            b = b*m_t + c*t;
            y = a*m_t + b*t;
        }
        return QPointF(x, y);
    }

    inline QPointF QBezier::normalVector(qreal t) const
    {
        qreal m_t = 1. - t;
        qreal a = m_t * m_t;
        qreal b = t * m_t;
        qreal c = t * t;

        return QPointF((y2-y1) * a + (y3-y2) * b + (y4-y3) * c,  -(x2-x1) * a - (x3-x2) * b - (x4-x3) * c);
    }

    inline QPointF QBezier::derivedAt(qreal t) const
    {
        // p'(t) = 3 * (-(1-2t+t^2) * p0 + (1 - 4 * t + 3 * t^2) * p1 + (2 * t - 3 * t^2) * p2 + t^2 * p3)

        qreal m_t = 1. - t;

        qreal d = t * t;
        qreal a = -m_t * m_t;
        qreal b = 1 - 4 * t + 3 * d;
        qreal c = 2 * t - 3 * d;

        return 3 * QPointF(a * x1 + b * x2 + c * x3 + d * x4,
                        a * y1 + b * y2 + c * y3 + d * y4);
    }

    inline QPointF QBezier::secondDerivedAt(qreal t) const
    {
        qreal a = 2. - 2. * t;
        qreal b = -4 + 6 * t;
        qreal c = 2 - 6 * t;
        qreal d = 2 * t;

        return 3 * QPointF(a * x1 + b * x2 + c * x3 + d * x4,
                        a * y1 + b * y2 + c * y3 + d * y4);
    }
    
    inline void QBezier::parameterSplitLeft(qreal t, QBezier *left)
    {
        left->x1 = x1;
        left->y1 = y1;

        left->x2 = x1 + t * ( x2 - x1 );
        left->y2 = y1 + t * ( y2 - y1 );

        left->x3 = x2 + t * ( x3 - x2 ); // temporary holding spot
        left->y3 = y2 + t * ( y3 - y2 ); // temporary holding spot

        x3 = x3 + t * ( x4 - x3 );
        y3 = y3 + t * ( y4 - y3 );

        x2 = left->x3 + t * ( x3 - left->x3);
        y2 = left->y3 + t * ( y3 - left->y3);

        left->x3 = left->x2 + t * ( left->x3 - left->x2 );
        left->y3 = left->y2 + t * ( left->y3 - left->y2 );

        left->x4 = x1 = left->x3 + t * (x2 - left->x3);
        left->y4 = y1 = left->y3 + t * (y2 - left->y3);
    }
*/

    void Split(CBezier &pFirst,CBezier &pSecond)
    {
        //order of operations is important because pFirst or pSecond could be the same as this
        CPoint_d c=0.5*(m2+m3);
        pFirst.m2=0.5*(m1+m2);
        pSecond.m3=0.5*(m3+m4);
        pFirst.m1=m1;
        pSecond.m4=m4;
        pFirst.m3=0.5*(pFirst.m2+c);
        pSecond.m2=0.5*(pSecond.m3+c);
        pFirst.m4=0.5*(pFirst.m3+pSecond.m2);
        pSecond.m1=pFirst.m4;
    }

    template<typename tInterpreter>
    void Interpret(tInterpreter &pInt,double pTolerance=0.5)
    {
        CBezier lBeziers[10];
        int lLevels[10];

        lBeziers[0]=*this;
        lLevels[0]=9;

        CBezier *b=lBeziers;
        int *lLevel=lLevels;

        while(b>=lBeziers)
        {
            CPoint_d l41=b->m4-b->m1;
            double l=std::abs(l41.x)+std::abs(l41.y);
            double d;
            if(l>1.0)
            {
                d=std::abs(l41.x*(b->m1.y-b->m2.y)-l41.y*(b->m1.x-b->m2.x))+
                  std::abs(l41.x*(b->m1.y-b->m3.y)-l41.y*(b->m1.x-b->m3.x));
            }
            else
            {
                l=1.0;
                d=std::abs(b->m1.x-b->m2.x)+std::abs(b->m1.y-b->m2.y)+
                  std::abs(b->m1.x-b->m3.x)+std::abs(b->m1.y-b->m3.y);
            }

            if(d<pTolerance*l||*lLevel==0)
            {
                pInt.LineTo(b->m4);
                --b;
                --lLevel;
            }
            else
            {
                //split, second half of the polygon goes lower into the stack
                b->Split(*(b+1),*b);
                lLevel[1]=--lLevel[0];
                ++b;
                ++lLevel;
            }
        }
    }

/*
    QRectF bounds() const;
    qreal length(qreal error = 0.01) const;
    void addIfClose(qreal *length, qreal error) const;

    qreal tAtLength(qreal len) const;

    int stationaryYPoints(qreal &t0, qreal &t1) const;
    qreal tForY(qreal t0, qreal t1, qreal y) const;

    QBezier mapBy(const QTransform &transform) const;

    inline void parameterSplitLeft(qreal t, QBezier *left);
    inline void split(QBezier *firstHalf, QBezier *secondHalf) const;

    int shifted(QBezier *curveSegments, int maxSegmets,
                qreal offset, float threshold) const;

    QBezier bezierOnInterval(qreal t0, qreal t1) const;
    QBezier getSubRange(qreal t0, qreal t1) const;
*/

private:
    CPoint_d m1,m2,m3,m4;
};

/*namespace img*/ } /*namespace buola*/ }

#endif
